package com.googlecode.svnj.dav;

import java.util.HashMap;
import java.util.Map;

import com.googlecode.svnj.RepoContext;

/**
 * class holding info about DAV/SVN pseudo-resources.  Inspired by
 * 'struct dav_resource_private' in dav_svn.h
 * 
 * @author dave
 *
 */
public class SpecialResource implements SVNConstants {

    public static enum Type {
        NOT_SPECIAL(null),      /* we can't all be */
        ROOT_COLLECTION(""),    /* .../!svn/     */
        VER_COLLECTION("ver"),  /* .../!svn/ver/ */
        HIS_COLLECTION("his"),  /* .../!svn/his/ */
        WRK_COLLECTION("wrk"),  /* .../!svn/wrk/ */
        ACT_COLLECTION("act"),  /* .../!svn/act/ */
        //VCC_COLLECTION("vcc"),  /* .../!svn/vcc/ */
        BC_COLLECTION("bc"),    /* .../!svn/bc/  */
        BLN_COLLECTION("bln"),  /* .../!svn/bln/ */
        WBL_COLLECTION("wbl"),  /* .../!svn/wbl/ */
        VCC("vcc"),             /* .../!svn/vcc/NAME */
        //PARENTPATH_COLLECTION(""),/* see SVNParentPath directive */

        /* new types in HTTP protocol v2: */
        ME("me"),                 /* .../!svn/me   */
        REV_COLLECTION("rev"),    /* .../!svn/rev/ */
        REVROOT_COLLECTION("rvr"),/* .../!svn/rvr/ */
        TXN_COLLECTION("txn"),    /* .../!svn/txn/ */
        TXNROOT_COLLECTION("txr");/* .../!svn/txr/ */
        
        private final String value;
        Type(String s) {
            value = s;
        }
        public String toString() {
            return value != null ? value: "<none>";
        }
    }
    /* type of this resource, per the enum above */
    private final Type type;
    /* path to this resource inside the repository FS.  E.g., "/" is
     * the root of the repository.
     * This field will be null for 'special' resources, that do
     * not have corresponding resources inside the repo
     * 
     * This field should start with a slash
     */
    private String reposPath;
    /* the prefix on a URI related to the webserver, e.g. /svnj/my-repos */
    private String contextPrefix;
    /* relative uri, after webserver prefix
     * Being a relative path, this field should *not* start with a '/'
     */
    private String relUri;
    /* revision this resource specifies, else -1 */
    private long rev = -1;
    /* need to know the root (FIXME: HEAD??) revision of the repository in certain cases */
    private long rootRev;
    public SpecialResource(String contextPrefix, String fullUri, long rootRev) {
        /* sanity check: full uri should start with repoPrefix */
        if (!fullUri.startsWith(contextPrefix)) {
            throw new IllegalArgumentException(
                    "full URI '" + fullUri + "' expected to start with prefix '"
                    + contextPrefix + "'"
                    );
        }
        this.contextPrefix = contextPrefix;
        this.rootRev = rootRev;
        relUri = fullUri.substring(contextPrefix.length());
        p("parsing '" + relUri + "'");
        int ind = relUri.indexOf('!');
        if (ind == -1) {
            // just a file
            this.type = Type.NOT_SPECIAL;
            this.reposPath = relUri;
        } else {
            /* from '!svn/<component>/<other-stuff>'
             * pull out '<component>'
             */
            String special = relUri.substring(ind);
            
            if (!special.startsWith(SPECIAL_PREFIX + "/")) {
                throw new IllegalArgumentException(
                        "sub resource path '" + special + "' doesn't contain '"
                        + SPECIAL_PREFIX + "/' as expected"
                        );
            }
            // grab component
            String comp;
            ind = special.indexOf('/');
            int ind2 = special.indexOf('/', ind + 1);
            if (ind == -1) {
                throw new IllegalArgumentException(
                        "no slash after special prefix: '"
                        + special + "'"
                        );
            } else if (ind2 == -1) {
                comp = special.substring(ind + 1);
            } else {
                comp = special.substring(ind + 1, ind2);
            }
            type = fromString(comp);
            if (type == null) {
                throw new IllegalArgumentException(
                        "unknown SVN resource component '" + comp + "' in '"
                        + fullUri + "'"
                        );
            }
            if (ind2 == -1) {
                return;
            }
            // parse everything after the component based on type
            String after = special.substring(ind2 + 1); // just after 2nd slash
            p("looking at remainder: '" + after + "'");
            switch (type) {
            // .svn/bc/8/some/repos/path.txt
            case BC_COLLECTION:
                rev = Path.atol(after, -1);
                if (rev == -1)
                    throw new IllegalArgumentException(
                            "invalid baseline rev in '" + special + "'"
                            );
                // skip over digits, get everything after the path
                after = Path.subpath(after);
                if (after == null) {
                    this.reposPath = "/"; // refers to the root of the BC
                } else {
                    this.reposPath = Path.join("", after);
                }
                break;
            case REV_COLLECTION:
                rev = Path.atol(after, -1);
                if (rev == -1)
                    throw new IllegalArgumentException(
                            "invalid baseline rev in '" + special + "'"
                            );
                break;
            default:
                rev = -1;
                reposPath = null;
                break;
            }
        }
        
        /* always remove leading / from relUri */
        if (relUri.startsWith("/")) {
            relUri = relUri.substring(1);
        }
    }
    /* one of 'vcc' 'bln' 'bc' etc */
    private static Type fromString(String resName) {
        Type ret = typeMap.get(resName);
        return ret;
    }
    private static Map<String, Type> typeMap = new HashMap<String,Type>();
    static {
        Type[] types = Type.class.getEnumConstants();
        for (Type t : types) {
            if (t != Type.NOT_SPECIAL) {
                typeMap.put(t.toString(), t);
            }
        }
    }
    /**** ACCESSORS ****/
    public String getHref() {
        switch (type) {
        case REV_COLLECTION:
            return Path.join(contextPrefix, SPECIAL_PREFIX + "/bln/" + getRev());
        case VCC:
            // point to rev's baseline
            return Path.join(contextPrefix, SPECIAL_PREFIX + "/bln/" + rootRev);
        default:
        return Path.join(contextPrefix, relUri);
        }
    }
    public Type getType() { return type; }
    public String getReposPath() { return reposPath; }
    public String getContextPrefix() { return contextPrefix; }
    /** returns the remainder of the path after '!svn/<component>/...' e.g.
     *  '!svn/vcc/default' -> 'default'
     *  '!svn/bc/100' -> '100'
     *  '!svn/act/foo-bar-baz' -> 'foo-bar-baz'
     */
    public String getPathInfo() {
        switch (type) {
        case NOT_SPECIAL:
            return relUri;
        case ME:
            return null;
            default:
        String prefix = SVNConstants.SPECIAL_PREFIX + '/' + type.toString() + '/';
        int len = prefix.length();
        if (relUri.startsWith(prefix)) {
            return relUri.substring(len);
        }
        throw new IllegalStateException(
                "relative URI '" + relUri + "' does not start with expected prefix '" + prefix + "'"
                );
        }
    }
    public String getRelUri() { return relUri; }
    public long getRev() { return rev; }
    private static void p(String s) {
        System.out.println("[SpecialRes] " + s);
    }
}
